#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::_Inst;

// 实现CentralCache的构造函数
CentralCache::CentralCache()
{
    // 初始化工作
}

// 实现CentralCache的析构函数
CentralCache::~CentralCache()
{
    // 清理工作
}

// Central的函数，获取一个Span对象
// 参数1：list，SpanList对象，存储着内存
// 参数2：需要申请的字节大小
Span *CentralCache::GetOneSpan(SpanList &list, size_t byte_size)
{
    Span *it = list.Begin(); // 从list的头开始找内存
    while (it != list.End()) // 循环遍历，一直找到list的末尾
    {
        if (it->_freeList != nullptr) // 如果当前的it的_freeList不是空
        {
            return it; // 返回当前Span
        }
        else // 如果当前Span的_freeList时空
        {
            it = it->_next; // 继续指向下一个Span
        }
    }
    // 先把Central cache的桶锁解掉，这样如果其他线程释放内存对象回来，不会阻塞
    list._mtx.unlock();
    // 走到这里说明没有多余的span了，只能找page cache要
    PageCache::GetInstance()->GetMtx().lock();
    Span *span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(byte_size));
    span->_isUse = true;
    span->_objSize = byte_size;
    PageCache::GetInstance()->GetMtx().unlock();
    // 对获取的span进行切分，不需要加锁，因为这会让其他线程访问不到这个span
    //  计算span的大块内存的起始地址
    char *start = (char *)(span->_pageID << PAGE_SHIFT);
    // 计算大块内存的大小（字节数）
    size_t bytes = span->_n << PAGE_SHIFT;
    // end确定大块内存的边界，确保后续切割时不越界
    void *end = start + bytes;

    // 把大块内存切成小块内存连接起来
    // 先切一块下来做头，方便尾插
    span->_freeList = start;
    start += byte_size;
    void *tail = span->_freeList;
    while (start < end)
    {
        NextObj(tail) = start;
        tail = start;
        start += byte_size;
    }
    // 切好span之后，需要把span挂到桶里面去的时候，再加锁
    list._mtx.lock();
    list.PushFront(span);
    return span;
}

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void *&start, void *&end, size_t batchNum, size_t byte_size)
{

    size_t actualNum = 1;                       // 实际获取的对象个数
                                                // 如果不够batchNum，有多少拿多少
    size_t index = SizeClass::Index(byte_size); // 根据字节大小，计算对应的索引
    _SpanLists[index]._mtx.lock();              // 把相应索引的span加锁

    Span *span = GetOneSpan(_SpanLists[index], byte_size); // 从对应索引的SpanList获取了byte_size大小相应的Span
    assert(span && span->_freeList);

    start = span->_freeList; // 把start和end都指向现在span的_freeList
    end = start;

    // 现在有了span，就来获取内存，[start,end]
    for (size_t i = 0; i < batchNum - 1 && NextObj(end) != nullptr; i++)
    {
        end = NextObj(end);
        ++actualNum;
    }
    // 走完了循环之后，start就是ThreadCache能获得的内存
    span->_freeList = NextObj(end); // 让span的空闲指针指向end的下一个位置
    NextObj(end) = nullptr;         // 让end指针的下一个位置指向空(和原来的链表断开)
    span->_useCount += actualNum;
    _SpanLists[index]._mtx.unlock(); // 解锁

    return actualNum; // 返回实际申请到的内存数量
}

void CentralCache::ReleaseListToSpans(void *start, size_t size)
{
    size_t index = SizeClass::Index(size);
    _SpanLists[index]._mtx.lock();
    while (start)
    {
        //知道了start对应的是哪个span
        Span *span = PageCache::GetInstance()->MapObjectToSpan(start);
        //记录一下start的next
        void *next = NextObj(start);
        //下面两句是头插的逻辑，让当前start的next指针指向span->_freeList
        NextObj(start) = span->_freeList;
        //让span的_freeList指向start
        span->_freeList = start;
        span->_useCount--;
        // 说明span的切分出去的小块内存都回来了
        // 这个span快就可以再回收给page cache，pagecache就可以再尝试去做前后页的合并
        if (span->_useCount == 0)   //使用计数为0，说明都回来了
        {
            // 先把span从SpanList中删除
            _SpanLists[index].Erase(span);
            span->_freeList = nullptr;
            span->_next = nullptr;
            span->_prev = nullptr;

            // 释放span给pagecache时，使用pagecache的锁就可以了
            _SpanLists[index]._mtx.unlock();
            PageCache::GetInstance()->GetMtx().lock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
            PageCache::GetInstance()->GetMtx().unlock();
        }

        //让start指向next，这样就能不断地释放，让循环走空
        start = next;
    }

    _SpanLists[index]._mtx.unlock();
}